package com.mc.lp.database.dao.impl;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.JDBCException;
import org.hibernate.LockMode;
import org.hibernate.ObjectDeletedException;
import org.hibernate.ObjectNotFoundException;
import org.hibernate.QueryException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.StaleObjectStateException;
import org.hibernate.StaleStateException;
import org.hibernate.UnresolvableObjectException;
import org.hibernate.WrongClassException;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Property;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate4.HibernateJdbcException;
import org.springframework.orm.hibernate4.HibernateObjectRetrievalFailureException;
import org.springframework.orm.hibernate4.HibernateOptimisticLockingFailureException;
import org.springframework.orm.hibernate4.HibernateQueryException;
import org.springframework.orm.hibernate4.HibernateSystemException;
import org.springframework.stereotype.Repository;

import com.google.common.base.Preconditions;
import com.mc.lp.database.dao.IGenericDAO;
import com.mc.lp.database.exception.ConcurrentAccessException;
import com.mc.lp.shared.vo.KeyValue;
import com.mc.lp.shared.vo.KeyValueMap;

@Repository
public abstract class AbstractGenericHibernateDAO<T, ID extends Serializable, DAOImpl extends IGenericDAO<T, ID>>
		implements IGenericDAO<T, ID> {

	private static final long serialVersionUID = 1L;
	private Class<T> clazz;
	public Logger LOG = null;
	@SuppressWarnings("unchecked")
	public AbstractGenericHibernateDAO() {
		Type type = getClass().getGenericSuperclass();
		ParameterizedType paramType = (ParameterizedType) type;
		clazz = (Class<T>) paramType.getActualTypeArguments()[0];
		initLogger();
	}
	
	private void initLogger(){
		LOG= Logger.getLogger(this.clazz);
	}

	@Resource
	SessionFactory sessionFactory;

	public void setClazz(final Class<T> clazzToSet) {
		this.clazz = clazzToSet;
	}

	@SuppressWarnings("unchecked")
	public T findById(final Long id) throws DataAccessException {

		T entity = null;
		try {
			Preconditions.checkArgument(id != null);
			entity = (T) this.getCurrentSession().load(this.clazz, id);
		} catch (ObjectNotFoundException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (ObjectDeletedException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (UnresolvableObjectException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (WrongClassException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (StaleObjectStateException e) {
			throw new HibernateOptimisticLockingFailureException(e);
		} catch (StaleStateException e) {
			throw new HibernateOptimisticLockingFailureException(e);
		} catch (JDBCException e) {
			throw new HibernateJdbcException(e);
		} catch (QueryException e) {
			throw new HibernateQueryException(e);
		} catch (HibernateException e) {
			throw new HibernateSystemException(e);
		}

		return entity;
	}

	@SuppressWarnings("unchecked")
	public T findById(final Long id, Class clazz) throws DataAccessException {

		T entity = null;
		try {
			Preconditions.checkArgument(id != null);
			entity = (T) this.getCurrentSession().load(clazz, id);
		} catch (ObjectNotFoundException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (ObjectDeletedException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (UnresolvableObjectException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (WrongClassException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (StaleObjectStateException e) {
			throw new HibernateOptimisticLockingFailureException(e);
		} catch (StaleStateException e) {
			throw new HibernateOptimisticLockingFailureException(e);
		} catch (JDBCException e) {
			throw new HibernateJdbcException(e);
		} catch (QueryException e) {
			throw new HibernateQueryException(e);
		} catch (HibernateException e) {
			throw new HibernateSystemException(e);
		}

		return entity;
	}

	/**
	 * 
	 * @param id
	 * @return
	 * @throws DataAccessException
	 */
	@SuppressWarnings("unchecked")
	public T get(ID id) throws DataAccessException {

		T entity = null;

		try {

			entity = (T) this.getCurrentSession().get(this.clazz, id);
		} catch (ObjectNotFoundException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (ObjectDeletedException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (UnresolvableObjectException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (WrongClassException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (StaleObjectStateException e) {
			throw new HibernateOptimisticLockingFailureException(e);
		} catch (StaleStateException e) {
			throw new HibernateOptimisticLockingFailureException(e);
		} catch (JDBCException e) {
			throw new HibernateJdbcException(e);
		} catch (QueryException e) {
			throw new HibernateQueryException(e);
		} catch (HibernateException e) {
			throw new HibernateSystemException(e);
		}

		return entity;
	}

	@SuppressWarnings("unchecked")
	public T get(ID id, Class clazz) throws DataAccessException {

		T entity = null;

		try {

			entity = (T) this.getCurrentSession().get(clazz, id);
		} catch (ObjectNotFoundException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (ObjectDeletedException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (UnresolvableObjectException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (WrongClassException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (StaleObjectStateException e) {
			throw new HibernateOptimisticLockingFailureException(e);
		} catch (StaleStateException e) {
			throw new HibernateOptimisticLockingFailureException(e);
		} catch (JDBCException e) {
			throw new HibernateJdbcException(e);
		} catch (QueryException e) {
			throw new HibernateQueryException(e);
		} catch (HibernateException e) {
			throw new HibernateSystemException(e);
		}

		return entity;
	}

	@SuppressWarnings("unchecked")
	public List<T> findAll() throws DataAccessException {
		return findByCriteria();
	}

	@SuppressWarnings("unchecked")
	public List<T> findByExample(T exampleInstance, String[] excludeProperty)
			throws DataAccessException {

		try {

			Criteria crit = this.getCurrentSession().createCriteria(this.clazz);
			Example example = Example.create(exampleInstance);

			for (String exclude : excludeProperty) {

				example.excludeProperty(exclude);
			}

			crit.add(example);

			return crit.list();
		} catch (ObjectNotFoundException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (ObjectDeletedException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (UnresolvableObjectException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (WrongClassException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (StaleObjectStateException e) {
			throw new HibernateOptimisticLockingFailureException(e);
		} catch (StaleStateException e) {
			throw new HibernateOptimisticLockingFailureException(e);
		} catch (JDBCException e) {
			throw new HibernateJdbcException(e);
		} catch (QueryException e) {
			throw new HibernateQueryException(e);
		} catch (HibernateException e) {
			throw new HibernateSystemException(e);
		}
	}

	@SuppressWarnings("unchecked")
	public T save(T entity) throws DataAccessException {

		try {

			this.getCurrentSession().save(entity);
			return entity;
		} catch (ObjectNotFoundException e) {
			e.printStackTrace();
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (ObjectDeletedException e) {
			e.printStackTrace();
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (UnresolvableObjectException e) {
			e.printStackTrace();
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (WrongClassException e) {
			e.printStackTrace();
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (StaleObjectStateException e) {
			e.printStackTrace();
			throw new HibernateOptimisticLockingFailureException(e);
		} catch (StaleStateException e) {
			e.printStackTrace();
			throw new HibernateOptimisticLockingFailureException(e);
		} catch (ConstraintViolationException e) {
			e.printStackTrace();
			throw new HibernateJdbcException(e);
		} catch (JDBCException e) {
			e.printStackTrace();
			throw new HibernateJdbcException(e);
		} catch (QueryException e) {
			e.printStackTrace();
			throw new HibernateQueryException(e);
		} catch (HibernateException e) {
			e.printStackTrace();
			throw new HibernateSystemException(e);
		}
	}

	/**
	 * 
	 * @param entity
	 * @return
	 * @throws DataAccessException
	 */
	@SuppressWarnings("unchecked")
	public T saveOrUpdate(T entity) throws DataAccessException, ConcurrentAccessException {

		this.getCurrentSession().saveOrUpdate(entity);
		return entity;
	}

	public void create(final T entity) throws DataAccessException {
		Preconditions.checkNotNull(entity);
		this.getCurrentSession().persist(entity);
	}

	public void update(final T entity) throws DataAccessException, ConcurrentAccessException {
		Preconditions.checkNotNull(entity);
		this.getCurrentSession().merge(entity);
	}

	public void delete(final T entity) throws DataAccessException, ConcurrentAccessException {
		Preconditions.checkNotNull(entity);
		this.getCurrentSession().delete(entity);
	}

	public void deleteById(final Long entityId) throws DataAccessException,
			ConcurrentAccessException {
		final T entity = this.findById(entityId);
		Preconditions.checkState(entity != null);
		this.delete(entity);
	}

	protected final Session getCurrentSession() {
		return this.sessionFactory.getCurrentSession();
	}

	public <E> List<T> findByProperty(String property, E param) throws DataAccessException {
		Session session = getCurrentSession();
		Criteria criteria = session.createCriteria(this.clazz);
		@SuppressWarnings("unchecked")
		List<T> list = criteria.add(Property.forName(property).eq(param)).list();
		return list;
	}

	@SuppressWarnings("unchecked")
	public List<T> findByProperties(KeyValueMap map) throws DataAccessException {
		Preconditions.checkNotNull(map);
		Preconditions.checkArgument(map.isNotEmpty());
		Session session = getCurrentSession();
		Criteria criteria = session.createCriteria(this.clazz);
		for (KeyValue keyValue : map.getKeyValues()) {
			criteria.add(Property.forName(keyValue.getKey()).eq(keyValue.getValue()));
		}
		return criteria.list();
	}

	/**
	 * Use this inside subclasses as a convenience method.
	 */
	@SuppressWarnings("unchecked")
	public List<T> findByCriteria(Criterion... criterion) throws DataAccessException {

		try {

			Criteria crit = this.getCurrentSession().createCriteria(this.clazz);

			for (Criterion c : criterion) {

				crit.add(c);
			}

			crit.setLockMode(LockMode.READ);

			return crit.list();
		} catch (ObjectNotFoundException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (ObjectDeletedException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (UnresolvableObjectException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (WrongClassException e) {
			throw new HibernateObjectRetrievalFailureException(e);
		} catch (StaleObjectStateException e) {
			throw new HibernateOptimisticLockingFailureException(e);
		} catch (StaleStateException e) {
			throw new HibernateOptimisticLockingFailureException(e);
		} catch (JDBCException e) {
			throw new HibernateJdbcException(e);
		} catch (QueryException e) {
			throw new HibernateQueryException(e);
		} catch (HibernateException e) {
			throw new HibernateSystemException(e);
		}
	}

}
